<template>
<div
    class="jsplumb-container"
    v-loading.fullscreen.lock="fullLoading"
    v-shortkey="{copy: ['ctrl','c'], delete: ['del'], macDelete: ['backspace'], paste:['ctrl','v'], rename: ['ctrl','r'], revoke: ['ctrl','z'], seeLog: ['ctrl','l'], execute: ['ctrl','shift','r']}"
    v-shortkey.push="{ctrl:['ctrl']}"
    @click="handleContainerClick"
    @shortkey="handleContainerHotKey"
    @drop="onDrop"
    @dragover="onDragover"
    @contextmenu.stop="handleContainerRightClick">
    <span
        id="mainJsplumbBox"
        class="jsplumb-box"
        :style="Object.assign({},zoomVal,{'width':`${jsplumbProperty.containerWidth}px`,'height':`${jsplumbProperty.containerHeight}px`})">
        <template v-for="(node, index) in nodeList">
            <jsplumb-node
                v-model="nodeList[index]"
                :key="index"
                :ref="node.nodeId"
                :jsplumb-instance="instance"
                :isCtrl="isCtrl"
                @on-node-click="handleNodeClick"
                @on-node-right-click="handleNodeRightClick">
            </jsplumb-node>
        </template>
    </span>
    <easy-cm tag="jsplumbContainerContext" :itemWidth="180" :itemHeight="32" :itemSize="12" :list="currentContextList" @ecmcb="handleCmList" :underline="false" :arrow="true"></easy-cm>
    <check-assess v-if="checkAccess.show" :checkAccess="checkAccess"></check-assess>
    <check-logs v-if="logData.show" :logData="logData"></check-logs>
    <check-data v-if="checkData.show" :dialogData="checkData"></check-data>
    <model-detail v-if="detail.show" :detail="detail"></model-detail>
    <confirm-dialog v-if="reNameObj.show" :confirmObj="reNameObj" @save="reNameSave">
        <template v-if="['保存模型', '保存数据'].includes(reNameObj.title)">
            <div style="padding-top:16px;display:flex;">
                <span style="padding-right:12px;">描述</span>
                <el-input :maxlength="100" type="textarea" :rows="5" clearable placeholder="请输入描述" v-model="description"></el-input>
            </div>
        </template>
    </confirm-dialog>
    <check-copy v-if="expCopyList.show" :expCopyList="expCopyList"></check-copy>
    <analysis-report v-if="analysisReport.show" :detail="analysisReport"></analysis-report>
    <save-copy v-if="saveCopy.show" :saveCopy="saveCopy" @save="handleSaveCopy"></save-copy>
</div>
</template>

<script>
import {
    jsPlumb
} from 'jsplumb'
import JsplumbNode from './node'
import modelDetail from '@/views/detail'
import CheckCopy from './CheckCopy'
import AnalysisReport from '@/views/analysisReport'
import {
    getContainerContextList,
    getLinkContextList,
    getNodeContextList
} from '@/js/contextMenu'
import {
    mapGetters,
    mapMutations
} from 'vuex'
import CheckAssess from './CheckAssess'
import CheckLogs from './CheckLogs'
import CheckData from '@/views/data/DataDetail'
import ConfirmDialog from '@/components/ConfirmDialog'
import SaveCopy from '../SaveCopy'

export default {
    name: 'JsplumbContainer',
    components: {
        AnalysisReport,
        JsplumbNode,
        CheckAssess,
        ConfirmDialog,
        CheckData,
        modelDetail,
        CheckLogs,
        CheckCopy,
        SaveCopy
    },
    data () {
        return {
            nodesObj: {}, // 用来判断复制节点时候是否重复了
            isInit: true,
            // 开始节点配置
            sourceEndpoint: {
                endpoint: 'Dot',
                paintStyle: {
                    stroke: '#999',
                    fill: '#fff',
                    radius: 4,
                    strokeWidth: 1
                },
                hoverPaintStyle: {
                    fill: '#999',
                    stroke: '#999'
                },
                maxConnections: -1,
                isSource: true,
                connector: ['Bezier', {
                    curviness: 60
                }],
                dragOptions: {},
                overlays: [
                    ['Label', {
                        location: [0.5, 1],
                        label: 'Drag',
                        cssClass: 'endpointSourceLabel',
                        visible: false
                    }]
                ]
            },
            // 结束节点配置
            targetEndpoint: {
                endpoint: 'Dot',
                paintStyle: {
                    fill: 'transparent',
                    radius: 4
                },
                hoverPaintStyle: {
                    fill: '#999',
                    stroke: '#999'
                },
                maxConnections: 1,
                dropOptions: {
                    hoverClass: 'hover',
                    activeClass: 'active'
                },
                isTarget: true,
                overlays: [
                    ['Label', {
                        location: [0.5, -0.5],
                        label: 'Drop',
                        cssClass: 'endpointTargetLabel',
                        visible: false
                    }]
                ]
            },
            ids: {},
            saveCopy: {
                show: false,
                title: '保存副本',
                form: {
                    snapshotName: '',
                    description: ''
                }
            },
            analysisReport: {
                show: false,
                moduleId: null // 29 单变量分析，30 双变量分析，31 关联分析，37 离散化分析
            },
            description: null,
            dragNode: null,
            expCopyList: {
                show: false,
                title: '查看实验副本'
            },
            logData: {
                show: false,
                title: '查看日志',
                data: []
            },
            detail: {
                show: false
            },
            fullLoading: false,
            reNameObj: {
                show: false,
                title: '重命名',
                size: 30,
                value: '',
                parameter: ''
            },
            checkData: {
                show: false,
                title: '查看数据',
                data: {}
            },
            checkAccess: {
                show: false,
                title: '查看模型评估报告'
            },
            types: ['', 'tableName', 'moduleId', 'modelId'],
            // jsplumb实例
            instance: null,
            // 画布缩放属性
            zoomVal: {
                '-webkit-transform': `scale(1)`,
                '-moz-transform': `scale(1)`,
                '-ms-transform': `scale(1)`,
                '-o-transform': `scale(1)`,
                'transform': `scale(1)`,
                'transformOrigin': '0% 0%'
            },
            // 画布缩放参数
            scale: 1,
            // 节点的一些固定属性
            nodeProperty: {
                // 节点的宽
                nodeWidth: 180,
                // 节点的高
                nodeHeight: 32,
                // 节点的最小边界展示大小
                minShow: 35
            },
            // 画布的一些相关属性
            jsplumbProperty: {
                // 画布适应时距上下的距离
                marginTopBottom: 60,
                // 画布适应时距左右的距离
                marginLeftRight: 60,
                // 节点容器宽高（主要用于中心点的计算）
                containerWidth: 50,
                containerHeight: 50,
                // 是否是框线状态
                selectDom: false,
                // 是否是框选结束
                isSelectOver: false,
                isClearSelect: true
            },
            // 当前右键菜单位置 （node节点，link节点，container容器）
            currentContextPosition: '',
            // 当前右键点击的节点id
            currentContextNodeId: '',
            // 当前右键点击的连线对象
            currentContextLine: null,
            // 当前容器右键的位置
            currentContainerContextPosition: {},
            // 检查节点状态的定时任务
            checkStatusIntervalId: null,
            isMounted: false,
            // 正在删除
            isDelete: false,
            // 正在按住ctrl
            isCtrl: false,
            // 点击连线
            isClickConnection: false
        }
    },
    computed: {
        ...mapGetters({
            srcExperimentId: 'common/getSrcExperimentId',
            selectedNodes: 'common/getSelectedNodes',
            copyNodeList: 'common/getCopyNodeList',
            canvasLoading: 'common/getCanvasLoading',
            projectId: 'common/getProjectId',
            typeId: 'common/getTypeId',
            canvasInfo: 'common/getCanvasInfo', // 获取当前实验下的画布信息
            nodeList: 'common/getNodeMap', // 获取当前实验下的节点信息
            linkList: 'common/getLinkMap', // 获取当前实验下的连线信息
            nodePortMapper: 'common/getNodePortMapper', // 节点端口关系
            experimentId: 'common/getExperimentId', // 获取当前实验id
            changedNodes: 'common/getChangedNodes', // 改变节点属性，受影响的节点
            currentExpInfo: 'common/getCurrentExpInfo'
        }),
        // 画布中心点位置信息转换为倍数
        jsplumbOrigin () {
            let transformOrigin = this.zoomVal.transformOrigin.split(' ')
            return {
                originX: transformOrigin[0].substring(0, transformOrigin[0].length - 1) / 100,
                originY: transformOrigin[1].substring(0, transformOrigin[1].length - 1) / 100
            }
        },
        // 当前右键菜单列表
        currentContextList () {
            if (this.currentContextPosition) {
                if (this.currentContextPosition === 'container') {
                    return getContainerContextList()
                } else if (this.currentContextPosition === 'node') {
                    return this.jsplumbNodeContextList
                } else if (this.currentContextPosition === 'link') {
                    return getLinkContextList()
                }
            }
            return []
        },
        // 节点右键菜单
        jsplumbNodeContextList () {
            let n = this.currentContextNodeId
            if (this.$isEmpty(n) || !this.nodeList.hasOwnProperty(n)) return []
            return getNodeContextList(this.nodeList[n])
        }
    },
    beforeDestroy () {
        if (!this.$isEmpty(this.instance)) {
            this.setLinkMap({})
            this.setNodeMap({})
            this.setCanvasInfo({})
            this.instance.reset()
            this.stopCheckNodeStatus()
        }
    },
    mounted () {
        this.$nextTick(() => {
            this.checkStatusIntervalId && window.clearInterval(this.checkStatusIntervalId)
            if (!this.canvasInfo.isInit && (this.canvasInfo.experimentId === this.experimentId)) {
                this.canvasInfo.isInit = true
                this.init()
                this.initDrag()
                this.initScroll()
                this.initData()
            }
        })
    },
    methods: {
        ...mapMutations({
            setSrcExperimentId: 'common/setSrcExperimentId',
            setSelectedNodes: 'common/setSelectedNodes',
            setCopyNodeList: 'common/setCopyNodeList',
            setCanvasLoading: 'common/setCanvasLoading',
            setCanvasInfo: 'common/setCanvasInfo',
            setNodeMap: 'common/setNodeMap',
            setLinkMap: 'common/setLinkMap',
            setShowNodeProp: 'common/setShowNodeProp',
            setModelDetail: 'common/setModelDetail',
            setRuningStatus: 'common/setRuningStatus',
            setUpdateModel: 'common/setUpdateModel',
            setUpdateDataTable: 'common/setUpdateDataTable',
            setAnalysisReportData: 'common/setAnalysisReportData'
        }),
        // 初始化数据
        initData () {
            this.loadJsplumbData({
                nodes: this.nodeList,
                links: this.linkList
            })
            this.fitCanvas()
            this.checkNodeStatus()
        },
        // 容器快捷键
        handleContainerHotKey ({ srcKey }) {
            let nodes = this.selectedNodes
            if (!nodes.length && !['ctrl', 'revoke'].includes(srcKey)) {
                this.$message.error('请选择节点')
                return
            }
            switch (srcKey) {
                case 'delete':
                case 'macDelete':
                    this.delNode()
                    if (!this.$isEmpty(this.currentContextLine)) {
                        this.deleteLink()
                    }
                    break
                case 'copy':
                    this.copy()
                    break
                case 'paste':
                    this.paste()
                    break
                case 'revoke':
                    this.revoke()
                    break
                case 'execute':
                    this.currentContextNodeId = nodes[0].id
                    this.execute(3)
                    break
                case 'seeLog':
                    if (!this.jsplumbNodeContextList.filter(item => item.id === 'seeLog').length) {
                        this.$message.error('该节点没有查看日志功能')
                        return
                    }
                    this.seeLog()
                    break
                case 'rename':
                    if (nodes.length > 1) {
                        this.$message.error('只能对一个节点重命名')
                        return
                    }
                    this.currentContextNodeId = nodes[0].id
                    this.rename()
                    break
                case 'ctrl':
                    this.handleCtrl()
                    break
            }
        },
        // ctrl键处理
        handleCtrl () {
            if (this.isCtrl) {
                this.isCtrl = false
                if (this.selectedNodes.length > 1) {
                    this.instance.addToDragSelection(this.selectedNodes)
                    this.jsplumbProperty.isClearSelect = false
                }
            } else {
                this.isCtrl = true
            }
        },
        checkCopy () {
            this.expCopyList.show = true
        },
        // 处理右键菜单
        handleCmList (indexList) {
            let clickItem = this.currentContextList[indexList[0]]
            for (let i = 1; i < indexList.length; i++) {
                clickItem = clickItem.children[indexList[i]]
            }
            if (this.$isEmpty(clickItem) || !this.$isEmpty(clickItem.children)) {
                return
            }
            if (clickItem.parameter) {
                this[clickItem.id](clickItem.parameter)
            } else {
                this[clickItem.id]()
            }
        },
        // 节点点击事件
        handleNodeClick (nodeId) {
            let flag = true
            if (!this.nodesObj[nodeId]) {
                this.nodesObj[nodeId] = true
            } else {
                flag = false
            }
            this.clearDomSelect()
            const node = document.getElementById(nodeId)
            if (this.isCtrl) {
                if (flag) {
                    let arr = this.selectedNodes
                    arr.push(node)
                    this.setSelectedNodes(arr)
                }
            } else {
                this.setSelectedNodes([node])
            }
            this.clearConnectionSelected()
        },
        // 画布容器右键点击事件
        handleContainerRightClick ($event) {
            this.clearDomSelect()
            // this.clearSelectedNodeStatus()
            this.clearConnectionSelected()
            this.currentContextPosition = 'container'
            this.$easycm($event, this.$root, 'jsplumbContainerContext')
            this.currentContainerContextPosition = this.getPositionByEvent($event)
        },
        // 容器点击事件
        handleContainerClick () {
            if (this.jsplumbProperty.isSelectOver) {
                this.jsplumbProperty.isSelectOver = false
                return
            }
            if (this.isClickConnection) {
                this.isClickConnection = false
                return
            }
            this.clearDomSelect()
            this.clearSelectedNodeStatus()
            this.clearConnectionSelected()
            this.setShowNodeProp(false)
        },
        // 节点右键点击事件
        handleNodeRightClick ({ nodeId, e, root }) {
            this.clearDomSelect()
            // this.clearSelectedNodeStatus()
            this.currentContextNodeId = nodeId
            this.currentContextPosition = 'node'
            this.setSelectedNodes([document.getElementById(nodeId)])
            this.$easycm(e, root, 'jsplumbContainerContext')
        },
        // 清除所有节点选中状态
        clearSelectedNodeStatus () {
            // 设置节点选中状态
            let nodeList = document.getElementsByClassName('jsplumb-node')
            for (let i = 0; i < nodeList.length; i++) {
                nodeList[i].classList.remove('jsplumb-node-selected')
            }
            // this.setSelectedNodes([])
        },
        // 清除所有线的选中状态
        clearConnectionSelected () {
            let connectionList = this.instance.getAllConnections()
            for (let item of connectionList) {
                if (item.hasType('normal-selected')) {
                    item.setType('normal')
                }
            }
        },
        // 清除所有节点的选中状态
        clearDomSelect () {
            if (!this.jsplumbProperty.isClearSelect) {
                this.instance.clearDragSelection()
                this.jsplumbProperty.isClearSelect = true
            }
        },
        close () {
            this.$message.warning('该功能尚未开发，敬请期待')
        },
        // 根据鼠标位置事件获取画布的实际坐标位置
        getPositionByEvent (e) {
            let { offsetLeft, offsetTop } = document.getElementById('mainJsplumbBox')
            let {
                originX,
                originY
            } = this.jsplumbOrigin
            // 缩放的中心点的坐标
            let wo = this.jsplumbProperty.containerWidth * originX
            let ho = this.jsplumbProperty.containerHeight * originY
            let { nodeWidth, nodeHeight } = this.nodeProperty
            let centerX = offsetLeft + wo
            let centerY = offsetTop + ho
            let x = (e.offsetX - (centerX - wo * this.scale + nodeWidth * 0.5 * this.scale)) / this.scale
            let y = (e.offsetY - (centerY - ho * this.scale + nodeHeight * 0.5 * this.scale)) / this.scale
            return {
                x,
                y
            }
        },
        getCenterWithNodeList (nodeList) {
            let boxMargin = {
                left: 999999,
                right: 999999,
                bottom: 999999,
                top: 999999
            }
            let { nodeWidth, nodeHeight } = this.nodeProperty
            if (this.$isEmpty(nodeList)) {
                let { marginLeftRight, marginTopBottom } = this.jsplumbProperty
                let { offsetWidth, offsetHeight } = this.$el
                boxMargin.left = marginLeftRight / 2
                boxMargin.right = offsetWidth - nodeWidth - marginLeftRight / 2
                boxMargin.top = marginTopBottom / 2
                boxMargin.bottom = offsetHeight - nodeHeight - marginTopBottom / 2
            } else {
                for (let i = 0; i < nodeList.length; i++) {
                    let { offsetLeft, offsetTop } = nodeList[i]
                    if (offsetLeft < boxMargin.left || boxMargin.left === 999999) {
                        boxMargin.left = offsetLeft
                    }
                    if (offsetTop < boxMargin.top || boxMargin.top === 999999) {
                        boxMargin.top = offsetTop
                    }
                    if (offsetLeft > boxMargin.right || boxMargin.right === 999999) {
                        boxMargin.right = offsetLeft
                    }
                    if (offsetTop > boxMargin.bottom || boxMargin.bottom === 999999) {
                        boxMargin.bottom = offsetTop
                    }
                }
            }
            boxMargin.bottom += nodeHeight
            boxMargin.right += nodeWidth
            boxMargin.centerX = boxMargin.left + (boxMargin.right - boxMargin.left) / 2
            boxMargin.centerY = boxMargin.top + (boxMargin.bottom - boxMargin.top) / 2
            return boxMargin
        },
        // 拖拽到指定区域了，进行的操作
        onDrop (e) {
            this.dragNode = JSON.parse(e.dataTransfer.getData('item'))
            if (Object.keys(this.nodeList).length > 128) {
                this.$message.error('很抱歉，当前节点数量已超过最大限制，如果想新增节点请先删除部分节点')
                return false
            }
            e.preventDefault()
            e.stopPropagation()
            let {
                x,
                y
            } = this.getPositionByEvent(e)
            this.addNode({
                positionX: x,
                positionY: y
            })
        },
        // 拖拽过程中阻止默认事件
        onDragover (e) {
            e.preventDefault()
        },
        // 初始化画布节点的边缘
        initBoxMargin () {
            let nodeList = document.getElementsByClassName('jsplumb-node')
            return this.getCenterWithNodeList(nodeList)
        },
        // 初始化画布拖拽
        initDrag () {
            let el = document.getElementById('mainJsplumbBox')
            this.$el.onmousedown = e => {
                let x = e.pageX - el.offsetLeft
                let y = e.pageY - el.offsetTop
                let div = null
                let startX = e.clientX
                let startY = e.clientY
                if (this.jsplumbProperty.selectDom) {
                    div = document.createElement('div')
                    div.className = 'select-dom'
                    div.style.left = `${startX}px`
                    div.style.top = `${startY}px`
                    document.body.appendChild(div)
                }
                let boxMargin = this.initBoxMargin()
                let {
                    originX,
                    originY
                } = this.jsplumbOrigin
                document.onmousemove = e => {
                    let left = e.pageX - x
                    let top = e.pageY - y
                    if (this.jsplumbProperty.selectDom) {
                        // 获取参数
                        div.style.width = `${Math.abs(e.clientX - startX)}px`
                        div.style.height = `${Math.abs(e.clientY - startY)}px`
                        let l = div.offsetLeft
                        let t = div.offsetTop
                        let w = div.offsetWidth
                        let h = div.offsetHeight
                        if (e.clientX - startX > 0) {
                            div.style.left = `${startX}px`
                        } else {
                            div.style.left = `${startX - Math.abs(e.clientX - startX)}px`
                        }
                        if (e.clientY - startY > 0) {
                            div.style.top = `${startY}px`
                        } else {
                            div.style.top = `${startY - Math.abs(e.clientY - startY)}px`
                        }
                        let nodeList = document.getElementsByClassName('jsplumb-node')
                        this.instance.clearDragSelection()
                        this.jsplumbProperty.isClearSelect = false
                        this.setSelectedNodes([])
                        for (let i = 0; i < nodeList.length; i++) {
                            nodeList[i].classList.remove('jsplumb-node-selected')
                            let pos = nodeList[i].getBoundingClientRect()
                            let sl = pos.width + pos.x
                            let st = pos.height + pos.y
                            if (sl > l && st > t && pos.x < l + w && pos.y < t + h) {
                                // 该DOM元素被选中，进行处理
                                nodeList[i].classList.add('jsplumb-node-selected')
                                let newNodeArr = this.selectedNodes
                                newNodeArr.push(nodeList[i])
                                this.setSelectedNodes(newNodeArr)
                            }
                        }
                        if (this.selectedNodes.length > 0) {
                            this.instance.addToDragSelection(this.selectedNodes)
                            this.jsplumbProperty.isClearSelect = false
                        }
                    } else {
                        let { containerWidth, containerHeight } = this.jsplumbProperty
                        let { minShow } = this.nodeProperty
                        let wo = containerWidth * originX
                        let ho = containerHeight * originY
                        let rw = (boxMargin.right - wo) * this.scale
                        let lw = (boxMargin.left - wo) * this.scale
                        let bh = (boxMargin.bottom - ho) * this.scale
                        let th = (boxMargin.top - ho) * this.scale
                        let { offsetHeight, offsetWidth } = this.$el
                        // 缩放的中心点的坐标
                        let centerX = left + wo
                        let centerY = top + ho
                        // 控制上下左右的移动边界
                        if (centerX + rw < minShow) {
                            left = minShow - rw - wo
                        }
                        if (centerX + lw > offsetWidth - minShow) {
                            left = offsetWidth - minShow - lw - wo
                        }
                        if (centerY + bh < minShow) {
                            top = minShow - bh - ho
                        }
                        if (centerY + th > offsetHeight - minShow) {
                            top = offsetHeight - minShow - th - ho
                        }
                        el.style.left = `${left}px`
                        el.style.top = `${top}px`
                    }
                }
                document.onmouseup = e => {
                    document.onmousemove = document.onmousedown = document.onmouseup = null
                    div != null && div.parentNode && div.parentNode.removeChild(div)
                    if (this.jsplumbProperty.selectDom) {
                        this.jsplumbProperty.isSelectOver = true
                        this.selectDom()
                    }
                }
                e.preventDefault()
            }
        },
        // 初始化画布滚动
        initScroll () {
            let el = document.getElementById('mainJsplumbBox')
            this.$el.onmousewheel = e => {
                el.style.left = `${el.offsetLeft + e.wheelDeltaX}px`
                el.style.top = `${el.offsetTop + e.wheelDeltaY}px`
            }
        },
        // 初始化jsplumb
        init () {
            let common = {
                DragOptions: {
                    cursor: 'pointer',
                    zIndex: 2000
                },
                ConnectionOverlays: [
                    ['Arrow', {
                        location: 1,
                        visible: true,
                        width: 5,
                        length: 5,
                        id: 'ARROW',
                        events: {
                            click () {
                                // alert('you clicked on the arrow overlay')
                            }
                        }
                    }]
                ]
            }
            // 注册连线类型配置
            let connectionTypes = {
                'normal': {
                    paintStyle: {
                        strokeWidth: 1,
                        stroke: '#999',
                        joinstyle: 'round',
                        outlineStroke: 'transparent',
                        outlineWidth: 5
                    },
                    hoverPaintStyle: {
                        strokeWidth: 2,
                        stroke: '#999',
                        outlineWidth: 5,
                        outlineStroke: 'transparent'
                    },
                    cssClass: 'connector-normal'
                },
                'normal-selected': {
                    paintStyle: {
                        strokeWidth: 1,
                        stroke: '#999',
                        joinstyle: 'round',
                        outlineStroke: 'transparent',
                        outlineWidth: 5
                    },
                    hoverPaintStyle: {
                        strokeWidth: 1,
                        stroke: '#999',
                        outlineWidth: 5,
                        outlineStroke: 'transparent'
                    },
                    cssClass: 'connector-normal'
                },
                'run': {
                    paintStyle: {
                        strokeWidth: 1,
                        stroke: '#2680eb',
                        joinstyle: 'round',
                        dashstyle: '8 2',
                        outlineStroke: 'white',
                        outlineWidth: 0
                    },
                    hoverPaintStyle: {
                        strokeWidth: 1,
                        stroke: '#2680eb',
                        joinstyle: 'round',
                        dashstyle: '8 2',
                        outlineStroke: 'white',
                        outlineWidth: 0
                    },
                    cssClass: 'connector-run'
                },
                'run-move': {
                    paintStyle: {
                        strokeWidth: 1,
                        stroke: '#2680eb',
                        joinstyle: 'round',
                        dashstyle: '8 2',
                        outlineStroke: 'white',
                        outlineWidth: 0
                    },
                    hoverPaintStyle: {
                        strokeWidth: 1,
                        stroke: '#2680eb',
                        joinstyle: 'round',
                        dashstyle: '8 2',
                        outlineStroke: 'white',
                        outlineWidth: 0
                    }
                }
            }
            // 注册endpoint类型配置
            let endpointTypes = {
                'normal': {
                    paintStyle: {
                        fill: 'transparent',
                        radius: 4
                    },
                    hoverPaintStyle: {
                        fill: '#999',
                        stroke: '#999'
                    },
                    cssClass: 'endpoint-normal'
                },
                'enable': {
                    paintStyle: {
                        fill: '#fff',
                        radius: 8,
                        stroke: '#A4CAF7',
                        strokeWidth: 8
                    },
                    cssClass: 'endpoint-blur'
                },
                'warn': {
                    paintStyle: {
                        fill: '#ff5c00',
                        radius: 4
                    },
                    hoverPaintStyle: {
                        fill: '#ff5c00',
                        stroke: '#ff5c00'
                    },
                    cssClass: 'endpoint-orange'
                }
            }
            // 设置节点共用属性
            this.instance = jsPlumb.getInstance(common)
            this.instance.setContainer('mainJsplumbBox')
            // 拖动创建的链接，再次拖动时设置不让链接断开
            this.instance.importDefaults({
                ConnectionsDetachable: false
            })
            // 注册连线类型
            this.instance.registerConnectionTypes(connectionTypes)
            // 注册连接点类型
            this.instance.registerEndpointTypes(endpointTypes)
            // 连线连接开始拖拽监听
            this.instance.bind('connectionDrag', ({ endpoints }) => {
                this.changeEndpointStyle(endpoints[0], true)
            })
            // 连线连接中断监听
            this.instance.bind('connectionAborted', ({ endpoints }) => {
                this.changeEndpointStyle(endpoints[0], false)
            })
            // 连线连接前监听
            this.instance.bind('beforeDrop', ({ connection, dropEndpoint }) => {
                // 起始点
                let sourcePoint = connection.endpoints[0]
                // 将要连接点
                let targetPoint = dropEndpoint
                let sourceCharTypeId = sourcePoint.getParameter('type')
                let targetCharTypeId = targetPoint.getParameter('type')
                if (!this.nodePortMapper.hasOwnProperty(sourceCharTypeId) || !this.nodePortMapper[sourceCharTypeId].hasOwnProperty(targetCharTypeId)) {
                    return false
                }
                // 查询两个节点是否已有连线
                let linkNum = this.instance.select({
                    source: sourcePoint.getParameter('nodeId'),
                    target: targetPoint.getParameter('nodeId')
                }).length
                if (linkNum > 0) {
                    return false
                }
                // 新增连线
                this.$post('/experiment/addDependency', {
                    projectId: this.projectId,
                    experimentId: this.experimentId,
                    srcNodeId: Number(sourcePoint.getParameter('nodeId')),
                    dstNodeId: Number(targetPoint.getParameter('nodeId')),
                    srcNodePortId: sourcePoint.getUuid(),
                    dstNodePortId: targetPoint.getUuid()
                }).then(data => {
                    if (data) {
                        const arr = data.relatedNode
                        for (let i = 0; i < arr.length; i++) {
                            if (arr[i].nodeId) {
                                let node = this.nodeList[arr[i].nodeId]
                                node.nodeState = arr[i].nodeState
                                this.$set(this.nodeList, arr[i].nodeId, node)
                            }
                        }
                        this.$message.success('连接成功')
                        this.getRuningStatus()
                    }
                }).catch(data => {
                    this.$message.error('连接失败')
                    // 新增失败删除之前的连线
                    this.instance.select({
                        source: sourcePoint.getParameter('nodeId'),
                        target: targetPoint.getParameter('nodeId')
                    }).delete()
                })
                return true
            })
            // 右键监听
            this.instance.bind('contextmenu', (component, originalEvent) => {
                if (component.getTypeDescriptor() === 'connection') {
                    this.currentContextPosition = 'link'
                    this.currentContextLine = component
                    this.$easycm(originalEvent, this.$root, 'jsplumbContainerContext')
                }
            })
            this.instance.bind('click', component => {
                if (component.getTypeDescriptor() === 'connection') {
                    if (this.selectedNodes.length > 0) {
                        this.clearSelectedNodeStatus()
                    }
                    this.clearConnectionSelected()
                    if (component.hasType('normal')) {
                        component.setType('normal-selected')
                        this.currentContextLine = component
                        this.isClickConnection = true
                    } else if (component.hasType('normal-selected')) {
                        component.setType('normal')
                    }
                }
            })
            // 连线连接监听
            this.instance.bind('connection', ({ connection }) => {
                connection.setType('normal')
                // 检查连接节点警告信息
                // this.checkWarnEndpoint(connection)
                // 还原连接后的样式
                this.changeEndpointStyle(connection.endpoints[0], false)
            })
        },
        // 删除节点接口
        delNode () {
            if (this.$isEmpty(this.selectedNodes) || this.isDelete) {
                return
            }
            this.isDelete = true
            let arr = this.selectedNodes
            let text = arr.length === 1 ? `确定要删除节点<span>[${arr[0].innerText}]</span>吗?` : '确定要删除所选中的节点吗?'
            this.$confirm(text, '提示', {
                confirmButtonText: '确 定',
                cancelButtonText: '取 消',
                dangerouslyUseHTMLString: true,
                type: 'warning'
            }).then(() => {
                const params = {
                    projectId: this.projectId,
                    experimentId: this.experimentId,
                    nodeIds: arr.map(v => v.id)
                }
                this.$post('/experiment/deleteNodes', params).then(res => {
                    if (res) {
                        for (let i = 0; i < arr.length; i++) {
                            this.$delete(this.nodeList, arr[i].id)
                        }
                        this.$nextTick(() => {
                            for (let i = 0; i < arr.length; i++) {
                                this.instance.remove(arr[i])
                            }
                        })
                        this.setSelectedNodes([])
                        this.isDelete = false
                        this.setShowNodeProp(false)
                        this.getRuningStatus()
                        this.$message.success('删除成功')
                    }
                }).catch(() => {
                    this.isDelete = false
                })
            }).catch(() => {
                this.isDelete = false
            })
        },
        // 添加节点
        addNode ({ positionX, positionY }) {
            const type = this.types[this.typeId]
            let params = {
                projectId: this.projectId,
                experimentId: this.experimentId,
                [type]: this.dragNode[type === 'tableName' ? 'fullName' : type],
                posX: positionX,
                posY: positionY
            }
            let addUrl = type === 'modelId' ? '/experiment/addModelSourceNode' : type === 'tableName' ? '/experiment/addDataSourceNode' : '/experiment/addNode'
            this.$post(addUrl, params).then(data => {
                if (data) {
                    const {
                        addNodes: [node]
                    } = data
                    node.nodeId = node.nodeId.toString() // 必须是string类型的id才能设置节点拖拽功能
                    this.$set(this.nodeList, node.nodeId, node)
                    this.$nextTick(() => {
                        this.setJsplumbNode(node)
                    })
                }
            })
        },
        // 检查连接目标设置样式
        changeEndpointStyle (endpoint, flag) {
            let sourceCharTypeId = endpoint.getParameter('type')
            let nodeId = endpoint.getParameter('nodeId')
            if (!this.nodePortMapper.hasOwnProperty(sourceCharTypeId)) {
                return
            }
            // ids的作用是防止重复循环
            if (!this.canvasLoading) {
                this.ids = {}
            }
            this.instance.selectEndpoints().each(ep => {
                if ((this.canvasLoading && this.ids[ep.id]) || !ep.getParameter('isTarget')) {
                    return
                }
                this.ids[ep.id] = ep.id
                if (ep.getParameter('nodeId') === nodeId) {
                    ep.setType('normal')
                } else if (!flag) {
                    ep.setType('normal')
                } else if (ep.isTarget && this.$isEmpty(ep.connections)) {
                    let hasType = this.nodePortMapper[sourceCharTypeId].hasOwnProperty(ep.getParameter('type'))
                    ep.setType(hasType ? 'enable' : 'normal')
                } else {
                    ep.setType('normal')
                }
            })
        },
        // 检查警告节点，设置提示
        checkWarnEndpoint (connection) {
            return true
        },
        // 框选节点
        selectDom () {
            this.jsplumbProperty.selectDom = !this.jsplumbProperty.selectDom
            let nodeList = document.getElementsByClassName('jsplumb-node')
            // 当框选状态时，移到画布上是十字型
            this.$el.style.cursor = this.jsplumbProperty.selectDom ? 'crosshair' : 'pointer'
            // 移到画布的每个节点上保持十字型
            for (let i = 0; i < nodeList.length; i++) {
                nodeList[i].style.cursor = this.jsplumbProperty.selectDom ? 'crosshair' : 'move'
            }
        },
        // 适应画布
        fitCanvas () {
            let { right, left, top, bottom } = this.initBoxMargin()
            // 节点的最大宽度和最大高度
            let width = right - left
            let height = bottom - top
            let box = document.getElementById('mainJsplumbBox')
            let { marginLeftRight, marginTopBottom, containerWidth, containerHeight } = this.jsplumbProperty
            let { offsetWidth, offsetHeight } = this.$el
            let wr = (offsetWidth - marginLeftRight) / width
            let hb = (offsetHeight - marginTopBottom) / height
            // 新的缩放因子
            this.scale = wr < hb ? wr : hb
            this.scale = this.scale > 1 ? 1 : this.scale < 0.2 ? 0.2 : this.scale
            this.getZoom(this.scale)
            this.$nextTick(() => {
                // 中心点x，y的相对倍数
                let {
                    originX,
                    originY
                } = this.jsplumbOrigin
                // 目标中心点的相对坐标
                let relativeCenterX = left + width / 2
                let relativeCenterY = top + height / 2
                let newLeft = box.offsetLeft + (containerWidth * originX - relativeCenterX)
                let newTop = box.offsetTop + (containerHeight * originY - relativeCenterY)
                box.style.left = newLeft + 'px'
                box.style.top = newTop + 'px'
                // 重置中心点
                this.getTransformOrigin()
            })
        },
        // 计算缩放的中心点
        getTransformOrigin () {
            let { offsetLeft, offsetTop } = document.getElementById('mainJsplumbBox')
            let { containerWidth, containerHeight } = this.jsplumbProperty
            let { offsetWidth, offsetHeight } = this.$el
            this.zoomVal.transformOrigin = `${(offsetWidth / 2 - offsetLeft) * 100 / containerWidth}% ${(offsetHeight / 2 - offsetTop) * 100 / containerHeight}%`
        },
        // 缩放方法（zoomIn放大，zoomOut缩小，restore还原）
        getZoom (f) {
            this.getTransformOrigin()
            let n = this.scale
            switch (f) {
                case 'zoomIn':
                    n += 0.05
                    break
                case 'zoomOut':
                    n -= 0.05
                    break
                case 'restore':
                    n = 1
                    break
                default:
                    n = f
                    break
            }
            if (n < 0.2) {
                this.$message.warning('您已缩小到极限了')
                n = 0.2
                return false
            }
            if (n > 1.7) {
                n = 1.7
                this.$message.warning('您已放大到极限了')
                return false
            }
            this.$set(this.zoomVal, '-webkit-transform', `scale(${n})`)
            this.$set(this.zoomVal, '-moz-transform', `scale(${n})`)
            this.$set(this.zoomVal, '-ms-transform', `scale(${n})`)
            this.$set(this.zoomVal, '-o-transform', `scale(${n})`)
            this.$set(this.zoomVal, 'transform', `scale(${n})`)
            this.instance.setZoom(n)
            this.scale = n
        },
        // 加载画布数据
        loadJsplumbData ({ nodes, links }) {
            this.$nextTick(() => {
                // 设置节点属性
                if (!this.$isEmpty(nodes)) {
                    let arr = Object.values(nodes)
                    for (let i = 0; i < arr.length; i++) {
                        if (!arr[i].isInit) {
                            this.setJsplumbNode(arr[i])
                            arr[i].isInit = true
                        }
                    }
                }
                // 设置连线
                if (!this.$isEmpty(links)) {
                    let arr = Object.values(links)
                    for (let i = 0; i < arr.length; i++) {
                        if (!arr[i].isInit) {
                            this.instance.connect({
                                uuids: [arr[i].srcPortId, arr[i].dstPortId],
                                parameters: {
                                    linkId: arr[i].linkId
                                }
                            })
                            arr[i].isInit = true
                        }
                    }
                }
                this.setCanvasLoading(false)
            })
        },
        // 设置节点可拖拽、拖拽范围、可连接
        setJsplumbNode ({ nodeId, inPorts, outPorts }) {
            this.instance.draggable(nodeId.toString(), {
                start: (selection) => {
                    // 拖动节点时将于本节点相关联的连线的运行状态更新为移动中，防止线偏移
                    if (this.nodeList[selection.el.id].nodeState === 3) {
                        let targetConnections = this.instance.getConnections({
                            target: selection.el.id
                        })
                        for (let item of targetConnections) {
                            item.setType('run-move')
                        }
                    } else if (this.nodeList[selection.el.id].nodeState === 4) {
                        let sourceConnections = this.instance.getConnections({
                            source: selection.el.id
                        })
                        for (let item of sourceConnections) {
                            if (this.nodeList[item.target.id].nodeState === 3) {
                                item.setType('run-move')
                            }
                        }
                    }
                },
                stop: ({
                    selection
                }) => {
                    let params = {
                        projectId: this.projectId,
                        experimentId: this.experimentId,
                        nodeIds: [],
                        posXs: [],
                        posYs: []
                    }
                    for (let item of selection) {
                        let n = item[0].id
                        let { left, top } = item[1]
                        params.nodeIds.push(n - 0)
                        let { nodeState } = this.nodeList[n]
                        // 在移动节点停止时将关联的连线状态更新为运动状态
                        if (nodeState === 3) {
                            let targetConnections = this.instance.getConnections({
                                target: n
                            })
                            for (let item of targetConnections) {
                                item.setType('run')
                            }
                        } else if (nodeState === 4) {
                            let sourceConnections = this.instance.getConnections({
                                source: n
                            })
                            for (let item of sourceConnections) {
                                if (this.nodeList[item.target.id].nodeState === 3) {
                                    item.setType('run')
                                }
                            }
                        }
                        params.posXs.push(left)
                        params.posYs.push(top)
                        this.nodeList[n].positionX = left
                        this.nodeList[n].positionY = top
                    }
                    this.$post('/experiment/updateNodePositions', params).then(() => {})
                }
            })
            if (inPorts.length > 0) {
                this.setNodePort('input', inPorts, nodeId.toString())
            }
            if (outPorts.length > 0) {
                this.setNodePort('output', outPorts, nodeId.toString())
            }
        },
        // 设置节点的port
        setNodePort (type, portList, nodeId) {
            let portsNumber = portList.length
            let xOffset = 1 / (portsNumber + 1)
            let x = 0
            for (let i = 0; i < portsNumber; i++) {
                // 点的位置
                let anchor = [0, 0, 0, 0]
                anchor[0] = x + xOffset
                x = anchor[0]
                if (type === 'output') {
                    anchor[1] = 1
                    anchor[3] = 1
                } else {
                    anchor[3] = -1
                }
                // 获取点所在的节点
                let node = document.getElementById(nodeId)
                if (this.$isEmpty(node)) {
                    continue
                }
                let uuid = portList[i].nodePortId
                let endPointInstance = this.instance.addEndpoint(node, type === 'output' ? this.sourceEndpoint : this.targetEndpoint, {
                    uuid,
                    anchor,
                    parameters: {
                        nodeId,
                        type: portList[i].type,
                        isTarget: type !== 'output'
                    }
                })
                // 增加输入输出点的提示信息
                endPointInstance.bind('mouseover', (endpoint, event) => {
                    // 鼠标移上去显示port提示
                    // TODO
                    endpoint.canvas.setAttribute('tooltip', `${portList[i].nodePortName}`)
                    endpoint.canvas.classList.add(`${type === 'output' ? 'port-tooltip-bottom' : 'port-tooltip-top'}`)
                })
                endPointInstance.bind('mouseout', (endpoint, event) => {
                    // 鼠标移出隐藏port提示
                    // TODO
                    endpoint.canvas.classList.remove(`${type === 'output' ? 'port-tooltip-bottom' : 'port-tooltip-top'}`)
                })
            }
        },
        reNameSave (value) {
            switch (this.reNameObj.title) {
                case '重命名':
                    this.handleReName(value)
                    break
                case '保存数据':
                    this.handleSaveDataTable(value)
                    break
                case '保存模型':
                    this.handleSaveModel(value)
                    break
            }
        },
        // 节点重命名接口
        handleReName (value) {
            this.$post('/experiment/renameNode', {
                projectId: this.projectId,
                experimentId: this.experimentId,
                nodeId: this.currentContextNodeId,
                newName: value
            }).then(res => {
                if (res) {
                    this.reNameObj.show = false
                    this.nodeList[this.currentContextNodeId].nodeName = value
                    this.$message.success('保存成功')
                }
            })
        },
        // 节点重命名事件
        rename () {
            this.reNameObj = {
                title: '重命名',
                show: true,
                size: 30,
                placeholder: '请输入节点名称',
                value: this.nodeList[this.currentContextNodeId].nodeName,
                parameter: ''
            }
        },
        // 查看分析报告
        checkReport (item) {
            this.fullLoading = true
            const params = {
                projectId: this.projectId,
                experimentId: this.experimentId,
                nodeId: item.nodeId
            }
            this.$post('/experiment/output/viewNodeOutputReport', params).then(data => {
                if (data) {
                    this.analysisReport = {
                        show: true,
                        moduleId: item.moduleId
                    }
                    this.setAnalysisReportData(data)
                }
            }).finally(() => {
                this.fullLoading = false
            })
        },
        // 删除节点事件
        deleteNode () {
            this.setSelectedNodes([document.getElementById(this.currentContextNodeId)])
            this.delNode()
        },
        // 删除连线
        deleteLink () {
            let [sourcePoint, targetPoint] = this.currentContextLine.endpoints
            let params = {
                projectId: this.projectId,
                experimentId: this.experimentId,
                srcNodeId: sourcePoint.getParameter('nodeId') - 0,
                dstNodeId: targetPoint.getParameter('nodeId') - 0,
                srcNodePortId: sourcePoint.getUuid(),
                dstNodePortId: targetPoint.getUuid()
            }
            this.$post('/experiment/removeDependency', params).then(data => {
                if (data) {
                    this.instance.deleteConnection(this.currentContextLine)
                    this.getRuningStatus()
                    this.$message.success('删除连线成功')
                    this.currentContextLine = null
                }
            }).catch(data => {
                this.$message.error('删除连线失败')
                // 恢复
                this.instance.connect({
                    uuids: [params.srcNodePortId, params.dstNodePortId]
                })
            })
        },
        // 复制节点
        copy () {
            if (this.$isEmpty(this.selectedNodes)) {
                return
            }
            let arr = []
            for (let i = 0; i < this.selectedNodes.length; i++) {
                arr.push(JSON.parse(JSON.stringify(this.nodeList[this.selectedNodes[i].id])))
            }
            this.setSrcExperimentId(this.experimentId)
            this.setCopyNodeList(arr)
        },
        // 粘贴节点
        paste () {
            if (this.$isEmpty(this.copyNodeList)) {
                return
            }
            let params = {
                projectId: this.projectId,
                experimentId: this.experimentId, // 当前实验id
                nodeIds: [],
                posXs: [],
                posYs: []
            }
            if (this.srcExperimentId !== this.experimentId) {
                params.srcExperimentId = this.srcExperimentId
            }
            if (this.$isEmpty(this.currentContainerContextPosition)) {
                for (let i = 0; i < this.copyNodeList.length; i++) {
                    let node = this.copyNodeList[i]
                    node.positionX += 50
                    node.positionY += 50
                    params.nodeIds.push(node.nodeId)
                    params.posXs.push(node.positionX)
                    params.posYs.push(node.positionY)
                }
            } else {
                let { x, y } = this.currentContainerContextPosition
                let nodeList = []
                for (let i = 0; i < this.copyNodeList.length; i++) {
                    let { positionX, positionY } = this.copyNodeList[i]
                    nodeList.push({
                        offsetLeft: positionX,
                        offsetTop: positionY
                    })
                }
                let center = this.getCenterWithNodeList(nodeList)
                for (let i = 0; i < this.copyNodeList.length; i++) {
                    let node = this.copyNodeList[i]
                    node.positionX += x - center.centerX + this.nodeProperty.nodeWidth
                    node.positionY += y - center.centerY + this.nodeProperty.nodeHeight
                    params.nodeIds.push(node.nodeId)
                    params.posXs.push(node.positionX)
                    params.posYs.push(node.positionY)
                }
                this.currentContainerContextPosition = {}
            }
            this.$post('/experiment/copyNode', params).then(data => {
                if (data) {
                    let addNodes = {}
                    let addLinks = {}
                    if (!this.$isEmpty(data.addNodes)) {
                        for (let i = 0; i < data.addNodes.length; i++) {
                            this.$set(this.nodeList, data.addNodes[i].nodeId, data.addNodes[i])
                            addNodes[data.addNodes[i].nodeId] = data.addNodes[i]
                        }
                    }
                    if (!this.$isEmpty(data.addLinks)) {
                        for (let i = 0; i < data.addLinks.length; i++) {
                            this.$set(this.linkList, data.addLinks[i].linkId, data.addLinks[i])
                            addLinks[data.addLinks[i].linkId] = data.addLinks[i]
                        }
                    }
                    this.loadJsplumbData({
                        nodes: addNodes,
                        links: addLinks
                    })
                }
            })
        },
        // 撤销删除节点
        revoke () {
            this.$post('/experiment/undoDeleteNodes', {
                projectId: this.projectId,
                experimentId: this.experimentId
            }).then(data => {
                if (data) {
                    let addNodes = {}
                    let addLinks = {}
                    if (!this.$isEmpty(data.addNodes)) {
                        for (let i = 0; i < data.addNodes.length; i++) {
                            this.$set(this.nodeList, data.addNodes[i].nodeId, data.addNodes[i])
                            addNodes[data.addNodes[i].nodeId] = data.addNodes[i]
                        }
                    }
                    if (!this.$isEmpty(data.addLinks)) {
                        for (let i = 0; i < data.addLinks.length; i++) {
                            this.$set(this.linkList, data.addLinks[i].linkId, data.addLinks[i])
                            addLinks[data.addLinks[i].linkId] = data.addLinks[i]
                        }
                    }
                    this.loadJsplumbData({
                        nodes: addNodes,
                        links: addLinks
                    })
                }
            })
        },
        // 停止检查节点状态
        stopCheckNodeStatus () {
            if (this.checkStatusIntervalId) {
                window.clearInterval(this.checkStatusIntervalId)
            }
        },
        // 查询各个节点状态，不需要开启定时器
        getRuningStatus () {
            const params = {
                projectId: this.projectId,
                experimentId: this.experimentId
            }
            this.$post('/experiment/queryExecutionStatus', params).then(res => {
                if (res && params.experimentId === this.experimentId) {
                    // 初始化实验时，获取正在运行中的实验已运行秒数时间
                    if (this.isInit && res.state === 2 && res.startTime) {
                        this.isInit = false
                        let t = parseInt(((new Date()).getTime() - (new Date(res.startTime)).getTime()) / 1000)
                        this.$emit('initTime', t)
                    }
                    this.setRuningStatus(res)
                    // 如果状态是未就绪和运行结束，则停止定时任务
                    if ((this.$isEmpty(res.state) || res.state === 0 || res.state === 3) && this.checkStatusIntervalId) {
                        window.clearInterval(this.checkStatusIntervalId)
                        this.$emit('stop-execute')
                    }
                    let arr = res.nodeExecInfos
                    for (let i = 0; i < arr.length; i++) {
                        let node = this.nodeList[arr[i].nodeId]
                        node.nodeState = arr[i].nodeState
                        node.startTime = arr[i].startTime
                        node.endTime = arr[i].endTime
                        node.costTime = arr[i].costTime
                        this.$set(this.nodeList, arr[i].nodeId, node)
                    }
                    let connectionList = this.instance.getAllConnections()
                    for (let i = 0; i < connectionList.length; i++) {
                        if (this.nodeList[connectionList[i].targetId].nodeState === 3) {
                            if (!connectionList[i].hasType('run-move')) {
                                connectionList[i].setType('run')
                            }
                        } else {
                            connectionList[i].setType('normal')
                        }
                    }
                }
            })
        },
        // 实验运行时，查询各个节点的状态
        checkNodeStatus () {
            this.checkStatusIntervalId = setInterval(() => {
                this.getRuningStatus()
            }, 3000)
        },
        // 从此处开始执行
        executeFromThis () {
            this.execute(1)
        },
        // 执行到此节点
        executeToThis () {
            this.execute(2)
        },
        // 执行此节点
        executeThis () {
            this.execute(3)
        },
        // 停止执行
        stopExecute () {
            const params = {
                projectId: this.projectId,
                experimentId: this.experimentId // 当前实验id
            }
            this.$post('/experiment/terminate', params).then(res => {
                if (res) {
                    this.getRuningStatus()
                }
            })
        },
        // 执行
        execute (executeType) {
            let params = {
                projectId: this.projectId,
                experimentId: this.experimentId,
                executeType: executeType || 0
            }
            if (executeType) {
                params['nodeId'] = this.currentContextNodeId
            }
            this.$post('/experiment/execution', params).then(data => {
                if (data) {
                    this.$emit('start-execute')
                    this.checkNodeStatus()
                }
            }).catch(() => {
                this.getRuningStatus()
                this.$emit('stop-execute')
            })
        },
        // 探索端口输出资源
        exploreNodeOutputResource (parameter) {
            return this.$post('/experiment/output/exploreNodeOutputResource', {
                projectId: this.projectId,
                experimentId: this.experimentId,
                nodeId: this.currentContextNodeId,
                sequence: parameter.sequence
            })
        },
        // 查看数据
        seeDataTable (parameter) {
            this.exploreNodeOutputResource(parameter).then((res) => {
                this.fullLoading = true
                this.$post('/dataSource/viewDataTable', {
                    projectId: this.projectId,
                    tableId: res
                }).then(tableData => {
                    if (tableData) {
                        tableData.summary.table = tableData.table
                        this.checkData = {
                            title: '查看数据',
                            show: true,
                            data: tableData.summary
                        }
                    }
                }).finally(() => {
                    this.fullLoading = false
                })
            })
        },
        // 保存数据接口
        handleSaveDataTable (value) {
            const {
                parameter
            } = this.reNameObj
            this.exploreNodeOutputResource(parameter).then((res) => {
                this.$post('/dataSource/saveTempDataTable', {
                    projectId: this.projectId,
                    tableId: res,
                    newTableName: value,
                    description: this.description
                }).then(tableData => {
                    if (tableData) {
                        this.setUpdateDataTable(Math.random())
                        this.reNameObj.show = false
                        this.$message.success('保存成功')
                    }
                })
            })
        },
        // 保存数据事件
        saveDataTable (parameter) {
            this.description = null
            this.reNameObj = {
                title: '保存数据',
                leftTitle: '名称',
                show: true,
                size: 64,
                placeholder: '请输入临时数据表名',
                value: '',
                parameter
            }
        },
        // 查看模型
        seeModel (parameter) {
            this.fullLoading = true
            this.exploreNodeOutputResource(parameter).then((res) => {
                this.$post('/modelSource/viewModel', {
                    projectId: this.projectId,
                    modelId: res
                }).then(modelData => {
                    if (modelData) {
                        this.setModelDetail(modelData)
                        this.detail.show = true
                    }
                }).finally(() => {
                    this.fullLoading = false
                })
            })
        },
        // 保存模型接口
        handleSaveModel (value) {
            const {
                parameter
            } = this.reNameObj
            this.exploreNodeOutputResource(parameter).then((res) => {
                this.$post('/modelSource/saveTempModel', {
                    projectId: this.projectId,
                    modelId: res,
                    newModelName: value,
                    experimentId: this.experimentId,
                    description: this.description
                }).then(modelData => {
                    if (modelData) {
                        this.setUpdateModel(Math.random())
                        this.reNameObj.show = false
                        this.$message.success('保存成功')
                    }
                })
            })
        },
        // 保存模型事件
        saveModel (parameter) {
            this.description = null
            this.reNameObj = {
                title: '保存模型',
                leftTitle: '名称',
                show: true,
                size: 64,
                placeholder: '请输入临时模型名称',
                value: this.nodeList[this.currentContextNodeId].nodeName,
                parameter
            }
        },
        // 导出PMML
        exportPMML () {
            this.$message.warning('该功能尚未开发，敬请期待')
        },
        // 查看日志
        seeLog () {
            this.fullLoading = true
            this.$post('/experiment/queryNodeLog', {
                projectId: this.projectId,
                experimentId: this.experimentId,
                nodeId: this.currentContextNodeId
            }).then(res => {
                if (res) {
                    this.logData.show = true
                    this.logData.data = res || []
                }
            }).finally(() => {
                this.fullLoading = false
            })
        },
        // 查看模型评估报告
        seeModelEvaluationReport () {
            this.fullLoading = true
            this.$post('/experiment/output/viewNodeOutputReport', {
                projectId: this.projectId,
                nodeId: this.currentContextNodeId,
                experimentId: this.experimentId
            }).then(res => {
                if (res) {
                    let data = {
                        summary: {
                            evaSummary: res
                        }
                    }
                    this.setModelDetail(data)
                    this.checkAccess = {
                        show: true,
                        title: '查看模型评估报告'
                    }
                }
            }).finally(() => {
                this.fullLoading = false
            })
        },
        // 查看自动调参报告
        seeTuneHyperParameterReport () {
            this.fullLoading = true
            this.$post('/experiment/output/viewNodeOutputReport', {
                projectId: this.projectId,
                nodeId: this.currentContextNodeId,
                experimentId: this.experimentId
            }).then(res => {
                if (res) {
                    let data = {
                        summary: {
                            evaSummary: res
                        }
                    }
                    this.setModelDetail(data)
                    this.checkAccess = {
                        show: true,
                        title: '模型自动调参报告'
                    }
                }
            }).finally(() => {
                this.fullLoading = false
            })
        },
        // 查看交叉验证报告
        seeCrossValidation () {
            this.fullLoading = true
            this.$post('/experiment/output/viewNodeOutputReport', {
                projectId: this.projectId,
                nodeId: this.currentContextNodeId,
                experimentId: this.experimentId
            }).then(res => {
                if (res) {
                    let data = {
                        summary: {
                            evaSummary: res
                        }
                    }
                    this.setModelDetail(data)
                    this.checkAccess = {
                        show: true,
                        title: '模型评估报告'
                    }
                }
            }).finally(() => {
                this.fullLoading = false
            })
        },
        // 查看统计分析报告
        seeStatisticalAnalytics () {
            // this.fullLoading = true
            // this.$post('/experiment/output/viewNodeOutputReport', {
            //     projectId: this.projectId,
            //     nodeId: this.currentContextNodeId,
            //     experimentId: this.experimentId
            // }).then(res => {
            //     if (res) {
            //         let data = {
            //             summary: {
            //                 evaSummary: res
            //             }
            //         }
            //         this.setModelDetail(data)
            //         this.analysisReport = {
            //             show: true,
            //             type: 1
            //         }
            //     }
            // }).finally(() => {
            //     this.fullLoading = false
            // })
            this.$message.warning('该功能尚未开发，敬请期待')
        },
        // 查看生成规则报告
        seeGenerateRules () {
            this.$message.warning('该功能尚未开发，敬请期待')
        },
        // 保存副本
        handleSaveCopy (value) {
            const param = {
                ...value,
                projectId: this.projectId,
                experimentId: this.experimentId
            }
            this.$post('/experiment/saveSnapshot', param).then(res => {
                if (res) {
                    this.saveCopy.show = false
                    this.$message.success('保存成功')
                }
            })
        },
        saveCope () {
            this.saveCopy = {
                show: true,
                title: '保存副本',
                form: {
                    snapshotName: this.currentExpInfo.experimentName,
                    description: ''
                }
            }
        },
        runHistory () {
            localStorage.setItem('experimentId', this.experimentId)
            localStorage.setItem('experimentName', this.currentExpInfo.experimentName)
            this.$router.push('expHistory')
        }
    },
    watch: {
        // 节点属性改变后，找到受影响的节点，并改变其状态
        changedNodes () {
            this.getRuningStatus()
        },
        jsplumbProperty: {
            handler (newVal, oldVal) {
                this.$emit('on-change-select-dom', newVal.selectDom)
            },
            deep: true
        }
    }
}
</script>

<style lang="less">
@import "./index.less";

.jsplumb-container {
    height: calc(100% - 40px);
    width: 100%;
    overflow: hidden;
    cursor: pointer;
    .node-icon {
        color: #2680eb;
    }

    .jsplumb-box {
        position: absolute;
        left: 0px;
        top: 0px;

        .jsplumb-node-selected {
            border: 1.5px solid #2680eb !important;
        }

        .jsplumb-node {
            border-radius: 2px;
            z-index: 1;
            cursor: move;
            background: #ffffff;
            position: absolute;
            width: 180px;
            height: 32px;
            border: 1.5px solid #ccc;
            border-radius: 4px;
            display: flex;
            align-items: center;
            justify-content: space-around;
            left: 0;
            top: 0;

            i {
                font-size: 16px;
            }

            span {
                width: 100px;
                font-size: 12px;
                white-space: nowrap;
                overflow: hidden;
                text-overflow: ellipsis;
            }
        }
    }
}

.select-dom {
    background: #e4e7ed;
    position: absolute;
    z-index: 2;
    opacity: 0.2;
}

.jtk-endpoint {
    cursor: crosshair;
    z-index: 2;
}

.port-tooltip-top:hover {
    &:before {
        width: 20px;
        height: 20px;
        position: absolute;
        top: -23px;
        right: -5px;
        background-color: #303133;
        content: '';
        transform: rotate(45deg);
        -webkit-transform: rotate(45deg);
        -moz-transform: rotate(45deg);
        z-index: 3;
    }

    &:after {
        min-width: 70px;
        text-align: center;
        line-height: 28px;
        position: absolute;
        white-space: nowrap;
        top: -33px;
        right: -30px;
        border-radius: 4px;
        background-color: #303133;
        color: #ffffff;
        font-size: 12px;
        padding: 0px 4px;
        content: attr(tooltip);
        z-index: 1001;
    }
}
.port-tooltip-bottom:hover {
    &:before {
        width: 20px;
        height: 20px;
        position: absolute;
        top: 20px;
        right: -5px;
        background-color: #303133;
        content: '';
        transform: rotate(45deg);
        -webkit-transform: rotate(45deg);
        -moz-transform: rotate(45deg);
        z-index: 3;
    }

    &:after {
        min-width: 70px;
        text-align: center;
        line-height: 28px;
        position: absolute;
        white-space: nowrap;
        top: 22px;
        right: -30px;
        border-radius: 4px;
        background-color: #303133;
        color: #ffffff;
        font-size: 12px;
        padding: 0px 4px;
        content: attr(tooltip);
        z-index: 1001;
    }
}
</style>
